package com.sanri.tools.modules.proxy.service;

import com.sanri.tools.modules.core.exception.ToolException;
import com.sanri.tools.modules.core.security.UserService;
import com.sanri.tools.modules.core.service.connect.ConnectService;
import com.sanri.tools.modules.core.utils.PoolHttpClient;
import com.sanri.tools.modules.proxy.service.dtos.ProxyInfo;
import com.sanri.tools.modules.proxy.service.dtos.RequestInfo;
import com.sanri.tools.modules.proxy.service.dtos.SimpleRequestInfo;
import com.sanri.tools.modules.proxy.service.sh.HttpClientSession;
import com.sanri.tools.modules.proxy.service.sh.RestClientResponse;
import com.sanri.tools.modules.proxy.service.sh.RestClientResponseImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.entity.ContentType;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.util.PropertyPlaceholderHelper;

import javax.script.*;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class HttpFileManager {
    @Autowired
    private ConnectService connectService;
    @Autowired
    private ProxyHttpService proxyHttpService;

    public static final String module = "httprequest";

    @Autowired(required = false)
    private UserService userService;


    /**
     * 请求列表
     *
     * @param connName
     * @return
     * @throws IOException
     */
    public List<SimpleRequestInfo> requests(String connName) throws IOException {
        final String content = connectService.loadContent(module, connName);
        final String[] lines = StringUtils.split(content, '\n');
        final List<RequestInfo> requestInfos = HttpFileParse.parseRequestInfos(Arrays.asList(lines.clone()));
        return requestInfos.stream().map(SimpleRequestInfo::new).collect(Collectors.toList());
    }

    /**
     * 获取请求详情
     *
     * @param connName
     * @param reqId
     * @return
     * @throws IOException
     */
    public RequestInfo detail(String connName, String reqId) throws IOException {
        final String content = connectService.loadContent(module, connName);
        final String[] lines = StringUtils.split(content, '\n');
        final List<RequestInfo> requestInfos = HttpFileParse.parseRequestInfos(Arrays.asList(lines.clone()));

        for (RequestInfo request : requestInfos) {
            if (request.getId().equals(reqId)) {
                return request;
            }
        }
        return null;
    }

    PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("{{", "}}");

    /**
     * 使用 http 文件的配置发送一个请求
     *
     * @param connName
     * @param reqId
     * @param params
     * @return
     */
    public CloseableHttpResponse sendRequest(String connName, String reqId, Map<String, String> params) throws IOException {
        final RequestInfo requestInfo = detail(connName, reqId);
        if (requestInfo == null) {
            throw new ToolException("没有找到请求: " + reqId + ", 在连接: " + connName);
        }

        Properties properties = new Properties();
        if (params != null) {
            properties.putAll(params);
        }

        ProxyInfo proxyInfo = new ProxyInfo();

        final RequestInfo.RequestLine requestLine = requestInfo.getRequestLine();
        final String method = propertyPlaceholderHelper.replacePlaceholders(requestLine.getMethod(), properties);
        final String url = propertyPlaceholderHelper.replacePlaceholders(requestLine.getUrl(), properties);
        proxyInfo.setMethod(method);
        final URL realUrl = new URL(url);
        proxyInfo.setSchema(realUrl.getProtocol());
        proxyInfo.setAddress(realUrl.getHost() + ":" + realUrl.getPort());
        proxyInfo.setPath(realUrl.getPath());
        proxyInfo.setQuery(realUrl.getQuery());

        final RequestInfo.Message message = requestInfo.getMessage();
        if (message != null) {
            final List<RequestInfo.Header> headers = message.getHeaders();
            for (RequestInfo.Header header : headers) {
                String field = propertyPlaceholderHelper.replacePlaceholders(header.getField(), properties);
                String value = propertyPlaceholderHelper.replacePlaceholders(header.getValue(), properties);
                proxyInfo.getHeaders().put(field, value);
            }

            final RequestInfo.Body body = message.getBody();
            if (body instanceof RequestInfo.TextBody) {
                RequestInfo.TextBody textBody = (RequestInfo.TextBody) body;
                final String content = textBody.getContent();
                String resolveContent = propertyPlaceholderHelper.replacePlaceholders(content, properties);
                proxyInfo.setBody(resolveContent);
            } else if (body instanceof RequestInfo.MultipartBody) {
                log.error("文件上传不支持, 太复杂");
                boolean support = false;
                if (support) {
                    CloseableHttpResponse closeableHttpResponse = multipartRequestHandle(requestInfo, properties, proxyInfo, (RequestInfo.MultipartBody) body);
                    // 登录信息写回
                    params.putAll(new HashMap(properties));
                }
            }
        }

        CloseableHttpResponse closeableHttpResponse = proxyHttpService.proxyRequest(proxyInfo);

        if (StringUtils.isNotBlank(requestInfo.getScript())) {
            Bindings bindings = handleScript(closeableHttpResponse, requestInfo.getScript(), properties);
            // 登录信息写回
            if (bindings != null) {
                HttpClientSession session = (HttpClientSession) bindings.get("HTTP_CLIENT_SESSION");
                Map<String, String> allVars = session.client.global.getAllVars();
                if (params != null){
                    params.putAll(allVars);
                }
            }
        }
        return closeableHttpResponse;
    }

    /**
     * 多部分的请求解析处理
     * @param requestInfo
     * @param properties
     * @param proxyInfo
     * @param body
     * @return
     * @throws IOException
     */
    private CloseableHttpResponse multipartRequestHandle(RequestInfo requestInfo, Properties properties, ProxyInfo proxyInfo, RequestInfo.MultipartBody body) throws IOException {
        List<PoolHttpClient.FormPart> formParts = new ArrayList<>();

        RequestInfo.MultipartBody multipartBody = body;
        for (RequestInfo.Message multipartBodyMessage : multipartBody.getMessages()) {
            final Map<String, String> headerMap = multipartBodyMessage.getHeaders().stream().collect(Collectors.toMap(RequestInfo.Header::getField, RequestInfo.Header::getValue));
            final String contentType = headerMap.get("Content-Type");
            final String disposition = headerMap.get("Content-Disposition");

            // 解析 disposition
            Map<String, String> dispositionMap = new HashMap<>();
            String[] items = StringUtils.split(disposition, ";");
            for (String item : items) {
                String[] keyValue = StringUtils.split(StringUtils.trim(item), "=");
                dispositionMap.put(keyValue[0], keyValue[1]);
            }

            // 创建一个部分
            PoolHttpClient.FormPart formPart = new PoolHttpClient.FormPart(contentType, dispositionMap.get("name"));
            formParts.add(formPart);

            MediaType mediaType = MediaType.parseMediaType(contentType);
            if (mediaType.includes(MediaType.APPLICATION_OCTET_STREAM)) {
                // 如果是流类型, 则需要读文件, 去哪读文件?
            } else {
                RequestInfo.TextBody textBody = (RequestInfo.TextBody) multipartBodyMessage.getBody();
                String content = textBody.getContent();
                String resolveContent = propertyPlaceholderHelper.replacePlaceholders(content, properties);
                if (StringUtils.isNotBlank(resolveContent)) {
                    formPart.setBody(resolveContent.getBytes(StandardCharsets.UTF_8));
                    formPart.setSize(formPart.getBody().length);
                }
            }

        }

        CloseableHttpResponse closeableHttpResponse = proxyHttpService.proxyMultipartRequest(proxyInfo, formParts);

        if (StringUtils.isNotBlank(requestInfo.getScript())) {
            Bindings bindings = handleScript(closeableHttpResponse, requestInfo.getScript(), properties);
            // 将绑定的数据, 通过参数写回去
            if (bindings != null) {
                HttpClientSession session = (HttpClientSession) bindings.get("HTTP_CLIENT_SESSION");
                Map<String, String> allVars = session.client.global.getAllVars();
                if (properties != null){
                    properties.putAll(allVars);
                }
            }
        }

        return closeableHttpResponse;
    }

    private ScriptEngineManager manager = new ScriptEngineManager();

    /**
     * 处理脚本
     * @param response
     * @param script
     * @param properties
     * @return
     */
    private Bindings handleScript(CloseableHttpResponse response, String script, Properties properties) throws IOException {
        if (StringUtils.isBlank(script)){
            return null;
        }

        Bindings bindings = new SimpleBindings();
        ScriptEngine scriptEngine = manager.getEngineByExtension("js");
        scriptEngine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);

        HttpEntity entity = response.getEntity();
        String content = EntityUtils.toString(entity);
        RestClientResponse restClientResponse = RestClientResponseImpl.createResponse(response,content );
        HttpClientSession session = HttpClientSession.create(restClientResponse);
        bindings.put("HTTP_CLIENT_SESSION",session);

        boolean isJson = StringUtils.equalsIgnoreCase(restClientResponse.getContentType().getMimeType(), ContentType.APPLICATION_JSON.getMimeType());
        String responseBody = isJson ? "JSON.parse(HTTP_CLIENT_SESSION.content)" : "HTTP_CLIENT_SESSION.content";
        String variableDefinition = "var client = { global: HTTP_CLIENT_SESSION.client.global };client.test = function(name, func) { return HTTP_CLIENT_SESSION.client.test(name, func || null);};client.assert = function(res, message) { return HTTP_CLIENT_SESSION.client.assertTrue(res, message || \"Assert failed\");};client.log = function(text) { print(text); };var response = { body: " + responseBody + ", status: HTTP_CLIENT_SESSION.statusCode, headers: HTTP_CLIENT_SESSION.headers, contentType: HTTP_CLIENT_SESSION.contentType};";
        try {
           scriptEngine.eval(variableDefinition + script);

        } catch (ScriptException e) {
            log.error("脚本执行出错: {}",e.getMessage(),e);
        }

        return bindings;
    }

}
